/*************************************************
 * Node.java                                     *
 * Defines a node and contains all associated    *
 * methods to manipulate nodes.                  *
 *************************************************/
package arena;

//Imports
import graphics.Point;
import java.awt.Color;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

/**
 * An arena is built using nodes.  Each node has a location, a color
 * and can be occupied by a robot.  Nodes are also linked with their
 * adjacent nodes by using the static method getAdjacentNodes.
 *
 * @author      Gabriel Loewen
 * @author      James Weston
 * @author      Jackie O'Quinn
 */
public class Node {

    //Defines colors which may be assigned to a node
    public static final Color VISITED = Color.BLACK;
    public static final Color UNKNOWN = Color.WHITE;
    public static final Color KNOWN = new Color(0x4096EE);
    public static final Color DELETED = new Color(0xB02B2C);
    //Defines whether or not the node is occupied by a robot
    public boolean isOccupied;
    //Defines the current color of the node
    private Color color;
    //Defines the current location of the node in (x,y) format
    private Point point;
    //Variables used by path driven behaviors
    public Node pathParent;
    public float costFromStart;
    public float estimatedCostToGoal;
    private int numVisits = 0;
    private List adjNodes = new LinkedList();

    /**
     * Constructor, assigns the node to a specific point in (x,y) format
     *
     * @param   pnt     Takes a point in (X,Y) format and creates a Node
     */
    public Node(Point pnt) {
        this.point = pnt;
        this.color = UNKNOWN;
    }

    /**
     * Returns the cost for path-driven behaviors.  Takes a node and returns the
     * cost from n to the goal
     *
     * @param   n     The node being tested.
     */
    public static float getCost(Node n) {
        return n.costFromStart + n.estimatedCostToGoal;
    }

    /**
     * Returns manhatten cost between node n1 and node n2
     *
     * @param   n1     The starting node
     * @param   n2     The ending node
     */
    public static float getCost(Node n1, Node n2) {
        //Returns manhattan distance
        return Math.abs(n2.getLocation().x - n1.point.x) + Math.abs(n2.getLocation().y - n1.point.y);
    }

    /**
     * Returns euclidean cost between node n1 and node n2
     *
     * @param   n1     The starting node
     * @param   n2     The ending node
     */
    public static float getEstimatedCost(Node n1, Node n2) {
        //Returns euclidean distance
        return (float) Math.abs(Math.sqrt(Math.pow(n2.getLocation().x - n1.point.x, 2) + Math.pow(n2.getLocation().y - n1.point.y, 2)));
    }

    /**
     * Returns a list of nodes adjacent to node n
     *
     * @param   n     The node being checked
     */
    public static List getAdjNodes(Node n) {
        List nodes = new ArrayList();
        Point point = n.getLocation();
        Arena arena = getArenaParent(n);
        if (point.x - 1 > -1) {
            nodes.add(arena.nodeMap.get((point.x - 1) + "," + point.y));
        }
        if (point.x + 1 < arena.width) {
            nodes.add(arena.nodeMap.get((point.x + 1) + "," + point.y));
        }
        if (point.y + 1 < arena.height) {
            nodes.add(arena.nodeMap.get(point.x + "," + (point.y + 1)));
        }
        if (point.y - 1 > -1) {
            nodes.add(arena.nodeMap.get(point.x + "," + (point.y - 1)));
        }
        return nodes;
    }
    
    public List getAdjNodes() {
        if (adjNodes.isEmpty()) {
            Arena arena = getArenaParent(this);
            if (point.x - 1 > -1) {
                adjNodes.add(arena.nodeMap.get((point.x - 1) + "," + point.y));
            }
            if (point.x + 1 < arena.width) {
                adjNodes.add(arena.nodeMap.get((point.x + 1) + "," + point.y));
            }
            if (point.y + 1 < arena.height) {
                adjNodes.add(arena.nodeMap.get(point.x + "," + (point.y + 1)));
            }
            if (point.y - 1 > -1) {
                adjNodes.add(arena.nodeMap.get(point.x + "," + (point.y - 1)));
            }
        }
        return adjNodes;
    }

    /**
     * Returns the Arena associated with node n.  Because there are two
     * types of arenas, physical and virtual, this method helps to
     * distinguish which arena this node is associated with.
     *
     * @param   node     The node being checked
     */
    public static Arena getArenaParent(Node node) {
        if (criep.CRIEP.VirtualArena.nodeMap.containsValue(node)) {
            return criep.CRIEP.VirtualArena;
        } else if (criep.CRIEP.PhysicalArena.nodeMap.containsValue(node)) {
            return criep.CRIEP.PhysicalArena;
        }
        return null;
    }

    /**
     * Sets the node color.  Available colors are BLACK, BLUE, and GREY.
     * These colors differentiate each node between visited (BLACK),
     * available (BLUE), and unknown (GREY).  This is necessary because
     * nodes which do not have a connecting path will be unavailable
     * to the robots.  This method is static.
     *
     * @param   n     The selected node.
     * @param   c     The color.
     */
    public static void setNodeColor(Node n, Color c) {
        n.setColor(c);
    }

    /**
     * Returns the node color.
     */
    public Color getColor() {
        return this.color;
    }

    /**
     * Sets the node color.  Available colors are BLACK, BLUE, and GREY.
     * These colors differentiate each node between visited (BLACK),
     * available (BLUE), and unknown (GREY).  This is necessary because
     * nodes which do not have a connecting path will be unavailable
     * to the robots.  This method is not static.
     *
     * @param   col     The color.
     */
    public void setColor(Color col) {
        this.color = col;
    }

    /**
     * Sets the color of the node to the correct color as well as
     * modifying the color of the adjacent nodes.
     *
     * @param   node    The node being occupied
     */
    public static void setOccupied(Node node) {

        node.isOccupied = true;

        node.setColor(VISITED);
        node.incrementVisits();
        List adjNodes = getAdjNodes(node);
        Iterator it = adjNodes.iterator();
        while (it.hasNext()) {
            Node n = (Node) it.next();
            if (n.getColor() != VISITED && n.getColor() != DELETED) {
                n.setColor(KNOWN);
            }
        }

    }

    /**
     * Returns the number of times this node has been visited.  This data
     * is accumulated for statistical purposes.
     *
     * @return The number of visits
     */
    public int getNumVisits() {
        return numVisits;
    }

    /**
     * Increments the number of visits this node has received.
     */
    public void incrementVisits() {
        this.numVisits++;
    }

    /**
     * Returns the location of the node as a Point in (x,y) format
     */
    public Point getLocation() {
        return this.point;
    }

    /**
     * Returns the location of the node as a String in "x,y" format
     *
     * @return Location of node (String)
     */
    @Override
    public String toString() {
        return this.point.toString();
    }
}
